/******************************************************************************
 * $Id: statusbar.c,v 1.34 2004/09/22 01:14:23 jimb Exp $
 * @file
 * @author  Jim Brooks http://www.jimbrooks.org
 * @brief   Statusbar functions.
 ******************************************************************************/

#include "common.h"
#include "statusbar.h"

#include "text.h"
#include "widgets.h"

#define STATUSBAR_CLEAR_STR          " "
#define STATUSBAR_TIMEOUT_MILSEC     (7 * 1000)

/******************************************************************************
 * Record a MEANINGFUL statusbar message.
 * Do not pass the line # part of a statusbar msg to this function.
 ******************************************************************************/
void
RecordStatusbarMsg( gchar* pMsg )
{
   g_string_free( pGStringStatusbarMsg, TRUE );
   pGStringStatusbarMsg = g_string_new( pMsg );
}

/******************************************************************************
 * @return The string being shown in the statusbar, or NULL if none.
 ******************************************************************************/
gchar*
GetStatusbarMsg( void )
{
   if (    pGStringStatusbarMsg == NULL
        || pGStringStatusbarMsg->str == NULL
        || pGStringStatusbarMsg->str[0] == '\0' )
      return NULL;

   return pGStringStatusbarMsg->str;
}

/******************************************************************************
 * Similar to PrintStatusbar() but message does not timeout.
 ******************************************************************************/
void
PrintStatusbarNoTimeout( gchar* pMsg )
{
   PrintStatusbar( pMsg );

  /*
   * Remove current time-out.
   */
   statusbarMsgTimeoutPending = FALSE;
   if ( timerStatusbar )
      gtk_timeout_remove( timerStatusbar );

   return;
}

/******************************************************************************
 * Print a message in the status bar.
 *
 * Will prepend the current line # into the message unless the msg actually
 * consists of " line: " or text widget is empty.
 *
 * IMPORTANT:
 * Consider possibility that passed string resides ephemerally on caller's stack,
 * or that caller afterwards will free the string.
 ******************************************************************************/
void
PrintStatusbar( gchar* pMsg )
{
   static gboolean prevMsgImportant = FALSE;
   static char     prevMsg[1000]    = "";

   if ( ! widgetsReady  ||  pMsg == NULL  ||  STREQ(pMsg,prevMsg) )
      return;

  /*
   * Prepend line # only if PrintLineNum() is not the caller
   * and the text widget is not empty.
   */
   if (    FindSubstr( pMsg, " line:" ) == NULL
        && !IsTextWidgetEmpty() )
   {
     /*
      * Another function is the caller.
      * Prepend the line #.
      */
      char* pStr = CreateString( 20 + strlen( pMsg) );
      sprintf( pStr, " line: %d     ", LineAtCursor()+1 );
      strcat( pStr, pMsg );
      gtk_statusbar_push( GTK_STATUSBAR( pWidgetStatusbar ),
                          statusbarContextId,
                          pStr );
      RecordStatusbarMsg( pMsg );		/* a copy of string will be recorded */
      DeleteString( pStr );

     /*
      * Time-out message.
      */
      EnableStatusbarTimeout();
   }
   else if (    FindSubstr( pMsg, " line:" ) != NULL
             && !IsTextWidgetEmpty() )
   {
     /*
      * PrintLineNum() is the caller and text widget is NOT empty.
      * Print the line # without erasing the current message.
      * Do so by prepending the passed "line: x" to the
      * existing _meaningful_ statusbar msg that was recorded.
      */
      GString* pGString;

      pGString = g_string_new( pMsg );
      pGString = g_string_append( pGString, "     " );  /* <-- space between line # and msg */
      pGString = g_string_append( pGString, pGStringStatusbarMsg->str );
      gtk_statusbar_push( GTK_STATUSBAR( pWidgetStatusbar ),
                          statusbarContextId,
                          pGString->str );
      g_string_free( pGString, TRUE );
   }
   else
   {
     /*
      * Text widget is empty.
      * Print the message verbatim.
      */
      GString* pGString;

      pGString = g_string_new( " " );				/* padding */
      pGString = g_string_append( pGString, pMsg );
      gtk_statusbar_push( GTK_STATUSBAR( pWidgetStatusbar ),
                          statusbarContextId,
                          pGString->str );
      g_string_free( pGString, TRUE );

      RecordStatusbarMsg( pMsg );		/* a copy of string will be recorded */

     /*
      * Time-out message.
      */
      EnableStatusbarTimeout();
   }

  /*
   * Ensure the statusbar is updated immediately.
   */
   ProcessPendingEvents();

  /*
   * Count this message.
   */
   ++statusbarMsgs;

  /*
   * Determine if this message was important or not
   * in order to prevent overwriting an important message
   * with a trivial one such as the current line #.
   */
   if (    FindSubstr( pMsg, "line:" )
        || STREQ( pMsg, STATUSBAR_CLEAR_STR ) )
      prevMsgImportant = FALSE;
   else
      prevMsgImportant = TRUE;
   strncpy( prevMsg, pMsg, sizeof(prevMsg) ); /* remember previous message */

   return;
}

/******************************************************************************
 * Similar to PrintStatusbarThirds() but message does not timeout.
 ******************************************************************************/
void
PrintStatusbarThirdsNoTimeout( gchar* pMsg1,
                               gchar* pMsg2,
                               gchar* pMsg3 )
{
   PrintStatusbarThirds( pMsg1, pMsg2, pMsg3 );

  /*
   * Remove current time-out.
   */
   statusbarMsgTimeoutPending = FALSE;
   if ( timerStatusbar )
      gtk_timeout_remove( timerStatusbar );

   return;
}

/******************************************************************************
 * Print a message, divided into 3 strings, in the status bar.
 ******************************************************************************/
void
PrintStatusbarThirds( gchar* pMsg1,
                      gchar* pMsg2,
                      gchar* pMsg3 )
{
   GString*	pgs;

  /*
   * Combine the 3 strings into one GString.
   */
   pgs = g_string_new( pMsg1 );
   g_string_append( pgs, pMsg2 );
   g_string_append( pgs, pMsg3 );

   PrintStatusbar( pgs->str );

   g_string_free( pgs, TRUE );

   return;
}

/******************************************************************************
 * Conditionally print a status message during startup.
 ******************************************************************************/
void
StartupStatusMsg( gchar* pMsg1,
                  gchar* pMsg2,
                  gchar* pMsg3 )
{
  /*
   * By default, print startup messages in statusbar,
   * but skip if user bothered to pass -speed or -q (-v overrides).
   */
   if (    argVerbose
        || (!argSpeed  &&  !argQuiet) )
   {
      DiscardStatusbarMsg();
      PrintStatusbarThirds( pMsg1, pMsg2, pMsg3 );
   }
}

/******************************************************************************
 * Print a message in the status bar, and associate a row of the list widget
 * with the message.
 ******************************************************************************/
void
PrintStatusbarForRow( gchar* pMsg,
                      gint   row )
{
   statusbarRow = row;
   PrintStatusbar( pMsg );
}

/******************************************************************************
 * Return the row that is associated with the current message in the status bar.
 * Returns ROW_NONE if no row is associated.
 ******************************************************************************/
gint
RowAssociatedWithStatusbar( void )
{
   return statusbarRow;
}

/******************************************************************************
 * Set the initial message shown in the status bar.
 * This function is one-shot.
 ******************************************************************************/
void
SetStatusbarInitialMsg( gchar* pMsg )
{
   if ( pStatusbarInitMsg == NULL )
   {
     /*
      * Clone string in case string resides on caller's stack.
      */
      pStatusbarInitMsg = CloneString( pMsg );

     /*
      * No clist widget row is associated with the status bar message.
      */
      statusbarRow = ROW_NONE;
   }
   return;
}

/*****************************************************************************
 * Discard the current message.
 *****************************************************************************/
void
DiscardStatusbarMsg( void )
{
   if ( widgetsReady )
      gtk_statusbar_pop( GTK_STATUSBAR( pWidgetStatusbar ), statusbarContextId );
}

/******************************************************************************
 * Clear all messages in the status bar.
 ******************************************************************************/
void
ClearStatusbar( void )
{
   ClearStatusbar_( 100 );
}
void
ClearStatusbar_( int limit )
{
   int cnt;

   if ( ! widgetsReady ) return;
g_return_if_fail( statusbarMsgs >= 0 );

  /*
   * Pop the stack of status bar messages.
   */
   for( cnt = 0; statusbarMsgs; --statusbarMsgs )
   {
      gtk_statusbar_pop( GTK_STATUSBAR( pWidgetStatusbar ),
                         statusbarContextId );

      if ( cnt > limit ) break;
   }

  /*
   * Disassociate any list widget row.
   */
   statusbarRow	= ROW_NONE;

  /*
   * Record empty msg.
   */
   RecordStatusbarMsg( "" );

   return;
}

/*****************************************************************************
 * Clear the statusbar as much as possible, but keep the line # visible.
 *****************************************************************************/
void
MostlyClearStatusbar( void )
{
  /*
   * Don't clear if timeout is pending.
   * Only print line # if text widget is not empty.
   */
   if ( ! statusbarMsgTimeoutPending )
   {
      ClearStatusbar();
      if ( ! IsTextWidgetEmpty() ) PrintStatusbar( STATUSBAR_CLEAR_STR );
   }
   return;
}

/*****************************************************************************
 * Enable the statusbar message to time-out.
 * @param   timerStatusbar (GLOBAL/IN/OUT)
 *          Handler of timer.
 *****************************************************************************/
void
EnableStatusbarTimeout( void )
{
  /*
   * Remove current time-out.
   */
   if ( timerStatusbar )
      gtk_timeout_remove( timerStatusbar );

  /*
   * Set time-out.
   */
   statusbarMsgTimeoutPending = TRUE;
   timerStatusbar = gtk_timeout_add( STATUSBAR_TIMEOUT_MILSEC, /* milli-seconds */
                                     StatusbarTimedOut,
                                     NULL );
   return;
}

/*****************************************************************************
 * Clear the statusbar after message has timed-out.
 *****************************************************************************/
gint
StatusbarTimedOut( gpointer unused )
{
  /*
   * Clear statusbar.
   */
   statusbarMsgTimeoutPending = FALSE;
   MostlyClearStatusbar();

  /*
   * hypersrc sometimes outruns ctags.pl.  If tags are still being processed,
   * don't let statusbar become blank (a mis-indication that program is ready).
   */
   if ( !tagProcessingComplete )
      PrintStatusbar( "Waiting for tag input..." );

  /*
   * Return FALSE to stop timer.
   */
   return FALSE;
}

/*****************************************************************************
 * Show percentage in statusbar.
 * @param   pMsg
 *          String must have the appendage "__%.", eg:
 *          "Modules searched: __%."
 *****************************************************************************/
void
StatusbarShowPercentage( char* pMsg,
                         int   percent )
{
   int		idx;
   char*    pMsg2;

  /*
   * Compute index to "__".
   */
   idx = strlen(pMsg) - 4 /*__%.*/ ;
#ifdef HYPERSRC_PEDANTIC
   g_return_if_fail( (idx > 0)  &&  (pMsg[idx+2] == '%')  &&  (pMsg[idx+3] == '.') );
#endif

  /*
   * Only modify a copy of string that surely resides in r/w memory.
   * The passed string might reside in r/o memory.
   */
   pMsg2 = (char*)g_malloc( strlen(pMsg) + 1 );
   strcpy( pMsg2, pMsg );

  /*
   * Round down to a multiple of 5%.
   */
   if ( percent % 5 ) percent = percent / 5 * 5;

  /*
   * Compute the percentage string.
   */
   if ( percent < 100 )
   {
      pMsg2[idx]   = '0' + percent / 10;
      pMsg2[idx+1] = '0' + percent % 10;
   }
   else
   {
      pMsg2[idx]   = '9';
      pMsg2[idx+1] = '9';
   }

   PrintStatusbarNoTimeout( pMsg2 );

   g_free( pMsg2 );

   return;
}

/*****************************************************************************
 * Print busy message in statusbar.
 *****************************************************************************/
void
StatusbarBusy( void )
{
   PrintStatusbar( "(busy)" );
}
